package javaNIO1.Selectors;

import javaNIO1.customerExeption.NotSupportedException;
import org.springframework.boot.diagnostics.FailureAnalysis;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Set;

/**
 * Created with Intel IDEA.
 * 创建选择器
 *
 * @Auther: gjt
 * @Date: 2024/7/27 10:18
 */
public class SelectorCreated {

    // 目前选择键的操作值
    public static final int OP_ACCEPT = 16;
    public static final int OP_CONNECT = 8;
    public static final int OP_READ = 1;
    public static final int OP_WRITE = 4;

    public static void main(String[] args) {


        /**
         * 注意不是非阻塞模式的通道不能注册到选择器上；
         * SocketChannel 只有连接后才有用并且只能连接一次；
         * ServerSocketChannel 和  SocketChannel 都是面向字节流的通道；即 TCP/IP
         * DatagramChannel 是面向数据包的通道；即 UDP/IP 并且 它可以充当服务器和客户端
         */

        try {
            // 构建选择器
            Selector selector = Selector.open();
            //使用之前检测选择器是否被打开瑞 如果没有被打开则会抛出ClosedSelectorException  非检查错误（运行时异常）
            if (!selector.isOpen()) {
                System.out.println("选择器没有被打开! 不能以下操作");
            }

            // 建立SocketChannel
            try {
                SocketChannel socketChannel = SocketChannel.open();
                // 设置非阻塞模式
                socketChannel.configureBlocking(false);

                socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
                // 检查是否连接成功
                boolean isConnected = socketChannel.isConnected();
                if (!isConnected) {
                    System.out.println("没有成功,做别的操作");
                }

                /**
                 *     public static final int OP_ACCEPT = 16;
                 *     public static final int OP_CONNECT = 8;
                 *     public static final int OP_READ = 1;
                 *     public static final int OP_WRITE = 4;
                 *
                 *     假设 socketChannel.validOps(); 返回了5  说明支持读和写操作
                 *     1: 0001   4: 0100
                 *     0001 | 0100 = 0101  (或操作)
                 *     1 或 4 等于 5 所以 可以进行读和写操作
                 */


                // 在注册之前检测此通道是否支持可选择的操作
                int i = socketChannel.validOps();
                // 假设对读操作感兴趣 需要检测
                if (!isCheckRead(i)) {
                    new NotSupportedException("Read");
                }

                /**
                 *  在注册之前刷新一下 选择器中的键（被取消的键将会清理） 并且注册到此选择器的通道也会被注销
                 *  不刷新的话 会出现 将此通道注册到一个相关的键已经被取消的选择器上，而通道仍处于被注册的状态的时候，通道不会在键被取消后立即注销，
                 *  直到下一次操作发生为止，就会抛出CancelledKeyException 异常
                 */
                selector.select();

                // 把通道注册到选择器上 对读操作感兴趣 会返回一个选择键
                SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);


                // 假设此通道已经准备就绪了  此判断说明此通道已经准备就绪了  后变得判断和前边一样

                if ((selectionKey.readyOps() & SelectionKey.OP_READ) != 0 || selectionKey.isReadable()) {
                    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                    buffer.clear();

                    SelectableChannel channel = selectionKey.channel();
                    // 检测子类 是否是 此父类的子类
                    if (channel instanceof SocketChannel) {
                        // 读取数据到缓冲区
                        ((SocketChannel) channel).read(buffer);
                        // 翻转
                        buffer.flip();
                    }
                }


            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            // 文件通道是不支持选择器的

            // 创建ServerSocketChannel
            try {
                ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                serverSocketChannel.socket().bind(new InetSocketAddress(8080));
                serverSocketChannel.configureBlocking(false);
                // 防止通道的非阻塞模式被修改；只有获取此对象的锁的线程才能对此通道的阻塞模式进行修改
                serverSocketChannel.blockingLock();

                // 在注册之前检测此通道是否支持可选择的操作
                int i = serverSocketChannel.validOps();
                // 假设对读操作感兴趣 需要检测
                if (!isCheckWrite(i)) {
                    new NotSupportedException("Write");
                }

                /**
                 *  在注册之前刷新一下 选择器中的键（被取消的键将会清理）并且注册到此选择器的通道也会被注销
                 *  不刷新的话 会出现 将此通道注册到一个相关的键已经被取消的选择器上，而通道仍处于被注册的状态的时候，通道不会在键被取消后立即注销，
                 *  直到下一次操作发生为止，就会抛出CancelledKeyException 异常
                 */
                selector.select();

                /**
                 * 把通道注册到选择器上 对写操作感兴趣, 并把后边的对象当作选择键的一个附件
                 * 如果选择键的存在时间长，但是附件存在时间短，记得使用完选择键后清理附件，否则附件不会被垃圾回收
                 */

                SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_WRITE, new NotSupportedException("你好"));

                //相当于
                SelectionKey register = serverSocketChannel.register(selector, SelectionKey.OP_WRITE);
                register.attach(new NotSupportedException("你好"));


                // 附件可以通过 attachment() 获取
                NotSupportedException attachment = (NotSupportedException) register.attachment();
                System.out.println(attachment.getMessage().toLowerCase() + "附件对象" );
            } catch (IOException e) {
                throw new RuntimeException(e);
            }


            // 创建DatagramChannel
            try {
                DatagramChannel datagramChannel = DatagramChannel.open();
                datagramChannel.configureBlocking(false);
                datagramChannel.connect(new InetSocketAddress("127.0.0.1", 8080));

                if (datagramChannel.isConnected()) {
                    System.out.println("通道没有被连接");
                }
                // 创建一个直接字节缓冲区
                ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                // 接收网络数据
                SocketAddress receive = datagramChannel.receive(buffer);


                // 在注册之前检测此通道是否支持可选择的操作
                int i = datagramChannel.validOps();
                // 假设对读操作感兴趣 需要检测
                if (!isCheckWrite(i)) {
                    new NotSupportedException("Read and Write");
                }

                /**
                 *  在注册之前刷新一下 选择器中的键（被取消的键将会清理） 并且注册到此选择器的通道也会被注销
                 *  不刷新的话 会出现 将此通道注册到一个相关的键已经被取消的选择器上，而通道仍处于被注册的状态的时候，通道不会在键被取消后立即注销，
                 *  直到下一次操作发生为止，就会抛出CancelledKeyException 异常
                 */
                selector.select();
                // 把通道注册到选择器上 对读和写操作都感兴趣
                datagramChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            // 将线程处于休眠状态 直到刚才刚兴趣的操作一个发生或者10 已经过去
            selector.select(1000);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private static boolean isCheckRead(int in) {
        // 只要包含此操作  与出来的值 就是此操作的值
        return (in & SelectionKey.OP_READ) == SelectionKey.OP_READ;
    }

    private static boolean isCheckWrite(int in) {
        return (in & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
    }

    private static boolean isCheckReadAndWrite(int in) {
        int temp = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
        return in == temp;
    }

    private static boolean isCheckAccept(int in) {
        return (in & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
    }

    private static boolean isCheckConnect(int in) {
        return (in & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
    }

}
